/*
 * Copyright 2010-2011 Marcin Blazejewski
 *
 * This file is part of Ground0.
 *
 * Ground0 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Ground0 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ground0. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <config.h>
#include "garage.hpp"
#include "wloader.hpp"
#include "camera.hpp"
#include "collision.hpp"

#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL/SDL.h>
#include <iostream>
#include <fstream>
#include <math.h>

#define FS  7.0f
#define model_rotvel   100.0

Garage::Garage() { 
    camera = new OrbitingCam( cam_focus, 12, M_PI/6, 0 );
    current = 0;
    rotate = true;
    show_cm = false;

    ambient.set_color(0.7f, 0.7f, 0.7f);

    for (int i = 0; i < 4; i++) {
        float v = 1.0f;
        lights[i].set_color(v,v,v);
    }
    lights[0].move( 20.0f,  50.0f,  20.0f);
    lights[1].move( 20.0f, -50.0f, -20.0f);
    lights[2].move(-20.0f, -50.0f,  20.0f);
    lights[3].move(-20.0f, -50.0f, -20.0f);
    lights[0].on = true;
    lights[1].on = true;
    lights[2].on = false;
    lights[3].on = false;
    relight();

    q_sphere = gluNewQuadric();
    gluQuadricDrawStyle(q_sphere, GLU_SILHOUETTE);   // GLU_FILL
}

Garage::~Garage() {
    delete camera;
    gluDeleteQuadric(q_sphere);
}

void Garage::add_ship_model(const char *name) {
    Block *b = get_model(name);
    CollisionModel *cm = get_cm(name);

    if (b && cm) {
        names.push_back(name);
        ships.push_back(b);
        cms.push_back(cm);
    }
    else {
        if (!b) cout << "[error ] no model for: " << name << endl;
        if (!cm) cout << "[error ] no CM for: " << name << endl;
        throw string("currupt model in the objbase: ") + string(name);
    }

    if (ships.size() == 1) {
        cme_node = cms[current]->nodes.begin();
    }
}

Block* Garage::get_current_model() {
    if ((current >= 0) && (current < ships.size())) {
        return ships[current];
    }

    throw string("no models");
}

void Garage::on_keydown(SDL_KeyboardEvent *ev) {
    bool ctrl = ev->keysym.mod & KMOD_CTRL;
    bool shift = ev->keysym.mod & KMOD_SHIFT;

    if (!ctrl && !shift) {
        switch (ev->keysym.sym) {
        case SDLK_LEFT:  switch_ship(-1); break;
        case SDLK_RIGHT: switch_ship(); break;
        case SDLK_SPACE: rotate = !rotate; break;
        case SDLK_e: show_cm = !show_cm; break;
        case SDLK_s: save_cm(); break;

        case SDLK_TAB: cme_next_node(); break;
        case SDLK_a: cme_add_node(); break;
        case SDLK_d: cme_delete_node(); break;
        case SDLK_EQUALS: cme_edit_node(Vector(0,0,0), 1); break;
        case SDLK_MINUS: cme_edit_node(Vector(0,0,0), -1); break;
        case SDLK_i: cme_edit_node(Vector(0, 0, -1), 0); break;
        case SDLK_k: cme_edit_node(Vector(0, 0,  1), 0); break;
        case SDLK_j: cme_edit_node(Vector(-1, 0, 0), 0); break;
        case SDLK_l: cme_edit_node(Vector(1, 0, 0), 0); break;
        case SDLK_u: cme_edit_node(Vector(0, 1, 0), 0); break;
        case SDLK_h: cme_edit_node(Vector(0, -1, 0), 0); break;

        }
    } else if (!ctrl && shift) {
        switch (ev->keysym.sym) {
        case SDLK_TAB: cme_next_node(-1); break;
        }
    }
}

void Garage::on_keyup(SDL_KeyboardEvent *ev) {
    bool ctrl = ev->keysym.mod & KMOD_CTRL;

    /*
    if (!ctrl) {
        switch (ev->keysym.sym) {
            //case SDLK_k:     cam_pos.x += 1; break;
            //default: return false;
        }
    } else {
        //return false;
    }
    return true;
    */

    //    if (parent) return parent->on_keyup(ev);
}

void Garage::on_mouse_down(struct SDL_MouseButtonEvent *ev) {
    if (ev->button == SDL_BUTTON_LEFT) {
        ((OrbitingCam*)camera)->set_edit(true);
        SDL_GetRelativeMouseState(NULL, NULL);
    } else if (ev->button == SDL_BUTTON_RIGHT) {
    } else if (ev->button == 4) {
        ((OrbitingCam*)camera)->zoom(-1);
    } else if (ev->button == 5) {
        ((OrbitingCam*)camera)->zoom(1);
    }
}

void Garage::on_mouse_up(struct SDL_MouseButtonEvent *ev) {
    ((OrbitingCam*)camera)->set_edit(false);
}

void Garage::tick(unsigned int ms) {    
    float dt = ms / 1000.f;
    
    camera->tick(ms);

    if (rotate) {
        orientation *= Quaternion(dt * M_PI/180 * model_rotvel, Vector::y_axis);
        orientation.normalize();
    }
}

void Garage::render() {
    static GLfloat m[16];

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // camera
    glLoadIdentity();
    /*
    gluLookAt(0, 5, -11,
              0, 0, 0,
              0, 1, 0);
    */
    camera->apply();
    glPushMatrix();

    // lights
    render_light();

    // floor
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_LIGHTING);

    glPopMatrix();
    glPushMatrix();
    draw_grid();

    // model
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_LIGHTING);
    glDisable(GL_BLEND);

    glPopMatrix();
    glPushMatrix();
    glTranslatef(pos.x, pos.y, pos.z);
    orientation.get_matrix(m);
    glMultMatrixf(m);

    ships[current]->draw();

    if (show_cm) {
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_LIGHTING);
        glEnable(GL_BLEND);
        draw_collision_model(*cms[current]);
    }

    glPopMatrix();
}

void Garage::draw_collision_model(CollisionModel &cm) {
    static GLfloat m[16];
    list<CollisionNode*>::iterator i;
    
    for (i = cm.nodes.begin(); i != cm.nodes.end(); i++) {
        CollisionSphere *cs = (CollisionSphere*)*i;

        if (i == cme_node) glColor3f(.8, .8, 0.2);
        else glColor4f(0.8, 0.8, 0.8, 0.3);

        glPushMatrix();
        glTranslatef(cs->center.x, cs->center.y, cs->center.z);
        gluSphere(q_sphere, cs->radius, 16, 32);
        glPopMatrix();
    }
}

void Garage::draw_grid() {
    const int NX = 40;
    const int NZ = 40;
    const float DX = .5f;
    const float DZ = .5f;
    const float Y = -2.0f;

    glColor3f(0, 0.7f, 0);

    glBegin(GL_LINES);
    for (int x = -NX; x <= NX; x++) {
        glVertex3f(x * DX, Y, -NZ * DZ);
        glVertex3f(x * DX, Y,  NZ * DZ);
    }

    for (int z = -NZ; z <= NZ; z++) {
        glVertex3f(-NX * DX, Y, z * DZ);
        glVertex3f( NX * DX, Y, z * DZ);
    }
    glEnd();

    glColor3f(0, 0, 0.05f);
    glBegin(GL_QUADS);
    glVertex3f(-NX * DX, Y, -NZ * DZ);
    glVertex3f(-NX * DX, Y,  NZ * DZ);
    glVertex3f( NX * DX, Y,  NZ * DZ);
    glVertex3f( NX * DX, Y, -NZ * DZ);
    glEnd();
}

void Garage::switch_ship(int d) {
    int next = current + d;
    
    if (next < 0) {
        next = ships.size() - 1;
    }
    else if (next >= ships.size()) {
        next = 0;
    }

    current = next;
    cme_node = cms[current]->nodes.begin();
}


void Garage::cme_next_node(int n) {
    CollisionModel *cm = cms[current];
    if (!cm) return;

    if (n > 0) {
        cme_node++;
        if (cme_node == cm->nodes.end()) 
            cme_node = cm->nodes.begin();
    }
    else {
        if (cme_node == cm->nodes.begin()) 
            cme_node = cm->nodes.end();
        cme_node--;
    }
}

void Garage::cme_edit_node(Vector dcenter, float dsize) {
    static float cme_scale = 0.1;
    CollisionSphere *cs = (CollisionSphere*)*cme_node;
    cs->center += (dcenter * cme_scale);
    cs->radius += (dsize * cme_scale);
    if (cs->radius <= 0) cs->radius -= dsize;
}

void Garage::cme_add_node() {
    CollisionModel *cm = cms[current];
    if (!cm) return;

    CollisionSphere *cs = new CollisionSphere();
    cs->radius = 1.0;
    cm->nodes.push_back(cs);
    cme_node = cm->nodes.end();
    cme_node--;
}

void Garage::cme_delete_node() {
    CollisionModel *cm = cms[current];
    if (!cm) return;

    list<CollisionNode*>::iterator tmp = cme_node;
    if (tmp != cm->nodes.begin()) tmp--;
    else tmp++;

    cm->nodes.erase(cme_node);
    cme_node = tmp;
}

void Garage::save_cm() {
    string cm_file = "data/" + names[current] + ".cm";
    cout << "[info  ] saving " << names[current] << " CM to " << cm_file << endl;

    ofstream ofs(cm_file.c_str(), ios_base::binary);
    cms[current]->save(ofs);
    ofs.close();
}
